##    Copyright 2007 David Blewett
##
##   Licensed under the Apache License, Version 2.0 (the "License");
##   you may not use this file except in compliance with the License.
##   You may obtain a copy of the License at
##
##       http://www.apache.org/licenses/LICENSE-2.0
##
##   Unless required by applicable law or agreed to in writing, software
##   distributed under the License is distributed on an "AS IS" BASIS,
##   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
##   See the License for the specific language governing permissions and
##   limitations under the License.
## Author: David Blewett
## Contact: david@dawninglight.net

"""Mailbox and related objects for the imap object model."""

__all__ = [
    'FetchCommand',
    'Mailbox',
    ]

from datetime import datetime
from email import Utils
import imaplib
from email.charset import CHARSETS, DEFAULT_CHARSET
from threading import local
from weakref import ref
from odict import OrderedDict
import commands, errors

__all__ = [
    'UniqueID'
    'Mailbox'
]


class UniqueID:
    """
    Class to encapsulate a uid+uid_validity.
    """
    def __init__(self, uid, uid_validity):
        self.uid = uid
        self.uid_validity = uid_validity

class Connection(local):
    """
        Class to encapsulate access to a specific mailbox on an IMAP4rev1 server.
    """
    def __init__(self, dsn, mbox='INBOX', debug=imaplib.Debug, charset=DEFAULT_CHARSET):
        self.dsn = dsn
        self._cxn = imaplib.IMAP4(self.dsn.hostname, self.dsn.port)
        self._cxn.debug = debug
        self.charset = charset
        self.state = []
        self.data = []
        
        return self.login(user=self.dsn.username, password=self.dsn.password, 
                          mailbox=mbox)

    def login(self, username, pw, mailbox='INBOX'):
        self._exec(commands.LOGIN, user=username, password=pw)
        return self._exec(commands.SELECT, mailbox=mbox)

    def close(self):
        #TODO: emit signal 
        self._exec(commands.CLOSE)

    #Utility functions
    def _exec(self, cmd, uid=True, *args, **kwargs):
        if uid:
            s, d = self._cxn.uid(cmd.method, *args, **kwargs)
        else:
            method = getattr(self._cxn, cmd.method)
            s, d = method(*args, **kwargs)
        #FIXME: Limit amount of state/data pairs saved
        self.state.insert(0, s)
        self.data.insert(0, d)
        return self._get_response(cmd, d)
    
    def _get_response(self, cmd, data):
        response = []
        for d in data:
            resp = {}
            for r in cmd.responses:
                d = self._cxn.response(r)[1][0]
                if d:
                    if d[0] == '(':
                        d = cmd.to_python[d][0]
                    else:
                        d = cmd.to_python[d]
                resp[cmd.responses[r]] = d
            response.append(resp)

        return resp

class Mailbox(Connection):
    """
    Class to wrap imaplib's mailbox-specific functionality.  
    """
    def __init__(self, dsn, name):
        kwargs = super(Mailbox, self).__init__(dsn, name)
        self.name = name
        self.flags = kwargs['flags']
        #RFC3501 specifies the EXISTS keyword to mean the number of messages in
        #the mailbox. IMHO, this term isn't very meaningful.
        self.msg_count = kwargs['exists']
        self.recent = kwargs['recent'] #Signifies number of messages with \Recent
                                       #flag set
        
        self.unseen = kwargs['unseen'] #The message sequence number of the first unseen
                            #message in the mailbox.  If this is missing, the
                            #client can not make any assumptions about the first
                            #unseen message in the mailbox, and needs to issue a
                            #SEARCH command if it wants to find it.
                                
        self.permanent_flags = kwargs['permanent_flags'] #A list of message flags that the
                                                #client can change permanently.
                                                #If this is missing, the client
                                                #should assume that all flags
                                                #can be changed permanently.
                                                    
        self.uid_next = kwargs['uid_next'] #The next unique identifier value.  If this is
                                #None, the client can not make any assumptions
                                #about the next unique identifier value.
                                
        self.uid_validity = kwargs['uid_validity'] #The unique identifier validity value.
                                        #If this is missing, the server does 
                                        #not support unique identifiers.
        self.children = []
        self.msg_dict = OrderedDict()

    def search(self, query, uid=True, limit=None):
        """Search messages in the currently selected mailbox

        This command is allowed in the Selected state.
        """
        data = self._exec(commands.SEARCH, uid, query)
        
        msg_list = [int(m) for m in data[0].split()]
        msg_list.sort()
        msg_list.reverse()
        if limit:
            msg_list = msg_list[:limit]
        return msg_list

    def fetch(self, messages, cmd, uid=True):
        """Retrieve one or more messages/message parts.

        @type messages: C{MessageSet}
        @param messages: The identifiers of messages to retrieve: '1', '1:5', '1,5,7,9'

        @type cmd: C{IMAPFetchCommand}
        @param cmd: IMAP command to execute.

        @type uid: C{bool}
        @param uid: If true, the IDs specified in the query are UIDs;
        otherwise they are message sequence IDs.

        @rtype: C{Message}
        """
        if isinstance(cmd, IMAPFetchCommand):
            raise errors.IllegalOperation('cmd must be an instance of IMAPFetchCommand.')
        msg_list = []
        data = self._exec(cmd, uid, messages, message_parts=str(cmd))

        #Coalesce data to a list of message headers
        #imaplib puts a ')' after every returned message  alt: [h[1] for h in data if h != ')']
        #msg_parts_raw = [h[1] for h in data[::2]]
        #for h in msg_parts_raw:
        if cmd.section:
            #tuple of tuples: (('string IMAP response', 'Requested header names: values'), ')')
            #clean up after imaplib
            response = [list(h) for h in data[::2]]
            for r in response:
                parse_response = self._parse_fetch_response(r[0], cmd)
                hp = HeaderParser()
                msg = hp.parsestr(r[1])
                imsg = Message(uid=parse_response['UID'], 
                                    flags=parse_response['FLAGS'], 
                                    internal_date=parse_response['INTERNALDATE'], 
                                    msg=msg)
                id = datetime(*Utils.parsedate(parse_response['INTERNALDATE']))
                msg_list.insert(0, imsg)
        else:
            #List of responses
            #FIXME: saw a few responses that were actually a tuple and not just a single string response
            for d in data:
                parse_response = self._parse_fetch_response(d, cmd)
                if 'ENVELOPE' in parse_response.keys():
                    msg = parse_response['ENVELOPE'].parse_envelope()
                    imsg = Message(uid=parse_response['UID'], 
                                        flags=parse_response['FLAGS'], 
                                        internal_date=parse_response['INTERNALDATE'],
                                        msg=msg)
                else:
                    imsg = Message(uid=parse_response['UID'], 
                                        flags=parse_response['FLAGS'], 
                                        internal_date=parse_response['INTERNALDATE'])
                id = datetime(*Utils.parsedate(parse_response['INTERNALDATE']))
                msg_list.insert(0, imsg)
                
        return msg_list

    #Methods to support the container protocol
    def __len__(self):
        return self.msg_count

    def __getitem__(self, key):
        raise errors.NotImplemented

    def __setitem__(self, key, value):
        raise errors.IllegalOperation

    def __delitem__(self, key):
        raise errors.NotImplemented

    def __iter__(self):
        raise errors.NotImplemented

    def __contains__(self, item):
        raise errors.NotImplemented

    def keys(self):
        raise errors.NotImplemented

    def values(self):
        raise errors.NotImplemented

    def items(self):
        raise errors.NotImplemented

    def has_key(self):
        raise errors.NotImplemented

    def get(self):
        raise errors.NotImplemented

    def clear(self):
        raise errors.NotImplemented

    def setdefault(self):
        raise errors.NotImplemented

    def iterkeys(self):
        raise errors.NotImplemented

    def itervalues(self):
        raise errors.NotImplemented

    def iteritems(self):
        raise errors.NotImplemented

    def pop(self):
        raise errors.NotImplemented

    def popitem(self):
        raise errors.NotImplemented

    def copy(self):
        raise errors.NotImplemented

    def update(self):
        raise errors.NotImplemented
